#include "Utils.h"
#include <wx/msgdlg.h>
#include <cmath>
#include <algorithm>
#include <ctype.h>

SQLInjection::SQLInjection()
{
    Init();
}

SQLInjection::~SQLInjection()
{

}

SQLInjection& SQLInjection::GetInstance()
{
    static SQLInjection obj;
    return obj;
}


void SQLInjection::setBlacklist()
{
    charBlacklist.push_back(wxString(_T("\"")));
    charBlacklist.push_back(wxString(_T("'")));
}

void SQLInjection::Init()
{
    setBlacklist();
    wxString reExpr(_T("["));

    for(std::vector<wxString>::iterator itr=charBlacklist.begin(); itr!=charBlacklist.end();++itr)
    {
        reExpr<<(*itr);
    }
    reExpr<<wxString("]");
    assert(re.Compile(reExpr));
}

///---------------------------------------------------------------------------------------------------------------------

XMLIntegrityValidator::XMLIntegrityValidator()
:ERROR_IDENTIFY_THRESHOLD(0.3f)
{

}

XMLIntegrityValidator::~XMLIntegrityValidator()
{

}

XMLIntegrityValidator& XMLIntegrityValidator::GetInstance()
{
    static XMLIntegrityValidator validator;
    return validator;
}

inline void XMLIntegrityValidator::clearData()    //to keep operation atomic
{
    m_statsData.clear();
    m_errors.clear();
}

//collect statistics data
void XMLIntegrityValidator::sampleXML(const tableType& table)
{
    clearData();

    std::map<wxString, wxString>::const_iterator map_itr;
    size_t tableSize = table.size();
    for(size_t rowIdx=0; rowIdx<tableSize; ++rowIdx)
    {
        for(map_itr=table[rowIdx].begin(); map_itr!=table[rowIdx].end();++map_itr)
        {
            if(m_statsData.count(map_itr->first)==0)
            {
                std::vector<size_t> hitRow;
                hitRow.reserve(64);
                m_statsData[map_itr->first] = hitRow;
            }
            m_statsData[map_itr->first].push_back(rowIdx);
        }
    }
}

//check if any unexpected data recieved (which is actually another column corrupted)
bool XMLIntegrityValidator::isValid(const tableType& table)
{
    bool bResult = true;

    std::map<wxString, std::vector<size_t> >::iterator itr=m_statsData.begin();
    const size_t refSize = table.size();
    const size_t errSize = ceil(refSize*ERROR_IDENTIFY_THRESHOLD);
    assert(errSize>1);

    while(itr!=m_statsData.end())
    {
        if(itr->second.size() < refSize)
        {
            bResult = false;
            if(itr->second.size() < errSize)
            {
                XMLErrorData error(itr->first, itr->second);
                //m_statsData will be used for generating reference for correctErrors. It should not cantain wrong columns
                m_statsData.erase(itr++);
                if(itr!=m_statsData.end())
                    error.nextTagName = itr->first;  //used for improve ref accuracy
                //duplicate in corruption is possible and should be allowed: foll ->following/followers_count
                //while(std::find(m_errors.begin(), m_errors.end(), error)!=m_errors.end()) error.wrongTagName.Append(' ');
                m_errors.push_back(error); //save a wrong tag name
            }
            //else a column is missing or just corrupted in place
            //this won't raise any error when SQL INSERT
        }
        else
        {
            ++itr;
        }
    }

    return bResult;
}

 //find all ref in case that RegEx hit multiple references
void XMLIntegrityValidator::findRef(tableType& table)
{
    const size_t refSize = table.size();
    wxRegEx re;
    wxString reExpr("");

    std::map<wxString, std::vector<size_t> >::iterator stats_itr;
    std::vector<XMLErrorData>::iterator errorData_itr = m_errors.begin();
    for(; errorData_itr!=m_errors.end(); ++errorData_itr)    //mostly only 1 error per sanityCheck execution
    {
        const wxString& wrongTag = errorData_itr->wrongTagName;
        const size_t corruptedRowSample = errorData_itr->hitRows.front();

        //Principle: a sina specific XML corruption is mostly a truncated column name
        //It always happens in a non-beginning place. Match by "begin with" can improve accuracy
        reExpr = "^"+wrongTag;
        if(!re.Compile(reExpr)) continue;

        stats_itr = m_statsData.begin();
        for(;stats_itr!=m_statsData.end();++stats_itr)
        {
            if(re.Matches(stats_itr->first)) //a similar col name found , used as reference
            {
                const wxString& refTag = stats_itr->first;
                const size_t refRowSample = stats_itr->second.front();

                //m_errors items are simply moved from statsData
                assert(refTag != wrongTag);
                //existed refs should not be a duplicat of current one
                assert(std::find(errorData_itr->refMatches.begin(), errorData_itr->refMatches.end(), RefFormat(refTag,0,1))==errorData_itr->refMatches.end());
                //sampled row and column must exists
                assert(corruptedRowSample<refSize && table[corruptedRowSample].count(wrongTag)>0);
                assert(refRowSample<refSize && table[refRowSample].count(refTag)>0);

                //evaluate weight
                int8_t weight = 0;
                const wxString& corruptedValueStr = table[corruptedRowSample][wrongTag];
                const wxString& refValueStr = table[corruptedRowSample+1][refTag];
                //if one of the column lost , there must be corruption
                if(stats_itr->second.size()<refSize) ++weight;
                //map isn't in order , but experiments suggest this's still useful.(actually , insert uses operator<)
                //especially when RegEx matches it, the corrupted one's often a duplicate of next tag
                if(refTag==errorData_itr->nextTagName) ++weight;
                //if the value type of the corrupted cell is identical to the current RegEx match , they may be brothers [t_t]
                if(!corruptedValueStr.IsEmpty() && !refValueStr.IsEmpty())  //usually a corrupted column is empty...
                {
                    if(corruptedValueStr.IsNumber() && refValueStr.IsNumber()) ++weight;
                    else if(corruptedValueStr.IsAscii() && refValueStr.IsAscii()) ++weight;

                    if(corruptedValueStr==refValueStr) ++weight;
                }

                errorData_itr->refMatches.push_back(RefFormat(refTag, weight, stats_itr->second.size()));
            }
        }

        std::sort(errorData_itr->refMatches.begin(), errorData_itr->refMatches.end());  //refMacthes.back() is the final answer
    }

}

void XMLIntegrityValidator::correctErrors(tableType& table)
{
    const size_t refSize = table.size();
    std::vector<XMLErrorData>::iterator errorData_itr = m_errors.begin();

    for(; errorData_itr!=m_errors.end(); ++errorData_itr)    //mostly only 1 error per sanityCheck execution
    {
        const wxString& wrongTagName = errorData_itr->wrongTagName;
        const wxString& correctTagName = errorData_itr->refMatches.back().refName;
        const size_t size_correctTagHitRow = errorData_itr->refMatches.back().refHitRowSize;

        for(size_t k=0;k<errorData_itr->hitRows.size();++k)
        {
            std::map<wxString, wxString>& tableSuspectRow = table[errorData_itr->hitRows[k]];

            if(size_correctTagHitRow==refSize)
            {
                //<scr></screen_name>
                //or
                //<scr/><screen_name></screen_name>

                //current wrong col name is just a corrupted duplication, need to remove from table
                if(!tableSuspectRow[wrongTagName].IsEmpty() && tableSuspectRow[correctTagName].IsEmpty())
                {
                    //valid XML value stays in the wrong column while the correct column is empty
                    tableSuspectRow[correctTagName] = tableSuspectRow[wrongTagName];
                }//else : the value is just null in both, or the right column is not empty
                tableSuspectRow.erase(wrongTagName);
                #ifdef XML_LOG
                LOGMAN.logInfo(wxString("XMLIntegrityValidator: removed corrupted and duplicated column[")<<wrongTagName<<"].");
                #endif
            }
            else if(size_correctTagHitRow<refSize)
            {
                //current wrong col name is corrupted in place:
                //<scr/>
                //<another_column></another_column>

                //replace the key to a right one
                tableSuspectRow.insert(std::make_pair(correctTagName, tableSuspectRow[wrongTagName]));
                tableSuspectRow.erase(wrongTagName);
                #ifdef XML_LOG
                LOGMAN.logInfo(wxString("XMLIntegrityValidator: replaced corrupted column[")<<wrongTagName<<wxString("] by column[")<<correctTagName<<"].");
                #endif
            }
            else
            {
                //stats_itr->second.size()>refSize : should not happen. corrupted columns still exist.
                assert(false);
                #ifdef XML_LOG
                LOGMAN.logError(wxString("XMLIntegrityValidator: corrupted columns still exist [")<<wrongTagName<<"].");
                #endif
            }
        }//end of errorData_itr->hitRows loop

    }//end of error list loop
}//end of function


void XMLIntegrityValidator::sanityCheck(tableType& table)
{
    wxCriticalSectionLocker validator_lock(XMLvalidator_CS);
    sampleXML(table);
    if(! isValid(table))
    {
        findRef(table);
        correctErrors(table);
    }
}



///------------------------------------------------------------------------------

CLog::CLog(const wxString& fileName)
:errorMsgCount(0)
,threadLogTarget(new wxLogStream(&outfile))
,threadGuiTarget(new wxLogGui())
{
    #ifdef LOG_USE_CPP_STREAM
    outfile.close();
    outfile.clear();
    outfile.open(fileName, std::ofstream::out|std::ofstream::app);
    #endif

    #ifdef LOG_USE_C_FILE
    if (pLogFile == NULL)
    {
        pLogFile = fopen( "log.txt", "a+" );
    }
    #endif

    wxLog::SetTimestamp("%x %H:%M:%S");
}

CLog::~CLog()
{
    delete threadLogTarget;
    threadLogTarget = NULL;

    delete threadGuiTarget;
    threadGuiTarget = NULL;

    if(outfile.is_open())
    {
        outfile.close();
    }
    outfile.clear();
}

CLog& CLog::GetInstance()
{
    static CLog logger;
    return logger;
}

void CLog::logError(const wxString& logMsg)
{
    /*  Singe thread
    wxCriticalSectionLocker lockLOGMAN(log_CS);
    if(wxThread::IsMain())
        delete wxLog::SetActiveTarget(new wxLogStream(&outfile));
    else
        wxLog::SetThreadActiveTarget(threadLogTarget);
    wxLogMessage( static_cast<const char*>(logMsg.c_str()) );
    //delete wxLog::SetActiveTarget(new wxLogGui());
    ++errorMsgCount;
    */

    if(wxThread::IsMain())
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        delete wxLog::SetActiveTarget(new wxLogStream(&outfile));
        wxLogMessage("%s", static_cast<const char*>(logMsg.c_str()) );
        delete wxLog::SetActiveTarget(new wxLogGui());
        ++errorMsgCount;
    }
    else
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        wxLog::SetThreadActiveTarget(threadLogTarget);
        wxLogMessage("%s", static_cast<const char*>(logMsg.c_str()) );
        wxLog::SetThreadActiveTarget(threadGuiTarget);
        ++errorMsgCount;
    }

}

void CLog::logInfo(const wxString& logMsg)
{
    if(wxThread::IsMain())
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        delete wxLog::SetActiveTarget(new wxLogStream(&outfile));
        wxLogMessage("%s", static_cast<const char*>(logMsg.c_str()) );
        delete wxLog::SetActiveTarget(new wxLogGui());
    }
    else
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        wxLog::SetThreadActiveTarget(threadLogTarget);
        wxLogMessage("%s", static_cast<const char*>(logMsg.c_str()) );
        wxLog::SetThreadActiveTarget(threadGuiTarget);
    }
}


///-----------------------------------------------------------------------------
URLEncoder::URLEncoder()
:bufferIndex(0)
{
    outputBufferQueue.reserve(URLENCODE_BUFFER_SIZE+1);
    for (size_t i=0; i<URLENCODE_BUFFER_SIZE ; ++i)
    {
        outputBufferQueue.push_back(wxString(""));
    }
}


URLEncoder& URLEncoder::GetInstance()
{
    static URLEncoder encoder;
    return encoder;
}

inline URLEncoder::BYTE
URLEncoder::toHex(const BYTE &x)
{
    return x > 9 ? x + 55: x + 48;
}

inline bool
URLEncoder::isUnreservedSpecialChar(const BYTE &c)
{
    return c=='-' || c=='_' || c=='.' || c=='~';
}

// from http://hi.baidu.com/kunp/blog/item/82549152b426e20f0cf3e3cd.html
wxString& URLEncoder::AsciiParse(const wxString& rawURL)
{
    wxCriticalSectionLocker encoderLock(URLencoder_CS);
     const size_t currentIndex = bufferIndex % URLENCODE_BUFFER_SIZE;
     outputBufferQueue[currentIndex].Clear();

     for( unsigned int a = 0; a < rawURL.Length(); ++a )
     {
         BYTE buf[4];
         memset( buf, 0, 4 );
         if( isalnum((BYTE)rawURL[a]) || isUnreservedSpecialChar((BYTE)rawURL[a]) )
         {
             buf[0] = rawURL[a];
         }
         else if (isspace( (BYTE)rawURL[a] ) )
         {
             buf[0] = '+';
         }
         else
         {
             buf[0] = '%';
             buf[1] = toHex( (BYTE)rawURL[a] >> 4 );
             buf[2] = toHex( (BYTE)rawURL[a] % 16);
         }

         outputBufferQueue[currentIndex] << (char *)buf;
     }

     ++bufferIndex; //swap output buffer
     return outputBufferQueue[currentIndex];
}

wxString& URLEncoder::UTF8Parse(const wxString& rawURL)
{
    // CS locker only ensure excluded accessing parse function
    // Output buffer may avoid result to be flushed before reading complete
    wxCriticalSectionLocker encoderLock(URLencoder_CS);
     const size_t currentIndex = bufferIndex % URLENCODE_BUFFER_SIZE;
     outputBufferQueue[currentIndex].Clear();

     const char* utf8_ptr = rawURL.ToUTF8();

     while(utf8_ptr && *utf8_ptr)  ///Potential infinite looping when UTF8 ends with non-null
     {
         BYTE buf[4];
         memset( buf, 0, 4 );
         if( isalnum((BYTE)(*utf8_ptr))  || isUnreservedSpecialChar((BYTE)(*utf8_ptr)) )
         {
             buf[0] = *utf8_ptr;
         }
         else if (isspace( (BYTE)(*utf8_ptr ) ))
         {
             buf[0] = '+';
         }
         else
         {
             buf[0] = '%';
             buf[1] = toHex( (BYTE)(*utf8_ptr) >> 4 );
             buf[2] = toHex( (BYTE)(*utf8_ptr) % 16);
         }

         outputBufferQueue[currentIndex] << (char *)buf;

         ++utf8_ptr;
     }

    ++bufferIndex; //swap output buffer
     return outputBufferQueue[currentIndex];
}
